#include "minicon.h"
#include "miniopt.h"

extern int err_ret(int errcode, const char *msg, const WCHAR *name);

//======================================================================================

// Stamp:  [[[[[CC]YY[/]]MM[/]]DD[-]]hh[:]]mm[.SS]
// Adjust: [-+][[[DD[-]]hh[:]]mm[:]]SS[.s{1,7}] 

static int gettimdata( const WCHAR *pw, unsigned *pui, unsigned& n100 )
{
   int nv = 0;
   n100 = 0;

   while ( *pw >= L'0' && *pw <= L'9' )
   {
      if ( nv >= 6 )
         return -6;

      unsigned v = (unsigned)(*pw - L'0');
      ++pw;
      if ( *pw >= L'0' && *pw <= L'9' )
      {
         v = 10*v + (unsigned)(*pw - L'0'); ++pw;
      }
      pui[nv++] = v;
      if ( *pw == L'/' || *pw == L':' || *pw == L'-' ) ++pw;
   }

   if ( *pw == L'.' )
   {
      ++pw;
      unsigned mul = 10*1000*1000;
      while ( *pw >= L'0' && *pw <= L'9' )
      {
         n100 = 10 * n100 + (unsigned)(*pw - L'0');
         mul /= 10;
         ++pw;
      }
      n100 *= mul;
   }

   return nv;
}

bool getUN( const WCHAR *pw, unsigned& ui )
{
   unsigned ua[6], n100;
   if ( gettimdata(pw, ua, n100) != 1) return false;
   ui = ua[0];
   return true;
}

bool getMS( const WCHAR *pw, DWORD& msec )
{
   unsigned ua[6], n100;
   int nv = gettimdata(pw, ua, n100);
   if ( nv > 3 || nv < 0 || (nv == 0 && n100 == 0) ) return false;
   
   unsigned ui = n100 / 10000;     // hundreds of nsec to msec
   if (nv > 0) ui += ua[--nv] * 1000; // add seconds
   if (nv > 0) ui += ua[--nv] * 1000*60; // minutes
   if (nv > 0) ui += ua[--nv] * 1000*60*60; // hours

   msec = (DWORD)ui;
   return true;
}

//======================================================================================
#if defined(_WIN64) || defined(_DEBUG) || !defined(_M_IX86)

static
void __forceinline llmuladd(__int64 &sum,unsigned __int32 mul,unsigned __int32 add)
{ sum = (__int64)((unsigned __int64)sum * mul + add); }

#else

__declspec(naked)
void __fastcall llmuladd(__int64 &sum,unsigned __int32 mul,unsigned __int32 add)
{
   // lea   ecx, sum
   // mov   edx, mul
   // push  add
   // call  llmuladd
   // stack:
   // [ add ] <- esp+8
   // [ ret ] <- esp+4
   // [ ebx ] <- esp
   #define sum_LO  dword ptr [ecx]
   #define sum_HI  dword ptr [ecx+4]
   #define addPRM  dword ptr [esp+8]
   #define mulPRM  edx
   __asm {
      push  ebx
      mov   eax, sum_LO
      mov   ebx, mulPRM
      mul   mulPRM
      add   eax, addPRM
      adc   edx, 0
      mov   sum_LO, eax
      xchg  ebx, edx
      mov   eax, sum_HI
      mul   mulPRM
      add   eax, ebx
      mov   sum_HI, eax
      pop   ebx
      ret   4
   }
   (void)sum, mul, add;
}

#endif
//======================================================================================

int touch(WCHAR **args, int na,
   bool aonly, bool monly, bool nocreate, char tmode, const WCHAR *pt, const WCHAR *pa)
{
   BOOL res;
   FILETIME atime, utime;
   HANDLE hf;
   unsigned v[6], n100=0;
   int i, rc = 0;

   if ( na <= 0 )
      return Opt::opt_err(Opt::Err_Missed_Param);
   
    ::GetSystemTimeAsFileTime(&atime);
    utime = atime;

    if ( tmode == 'r' ) // reference file time
    {
       res = 0;
       hf = ::CreateFileW(pt,
          GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
          NULL, OPEN_EXISTING,0,NULL
       );
       if ( INVALID_HANDLE_VALUE != hf )
       {
          res = ::GetFileTime(hf, NULL, &atime, &utime);
          ::CloseHandle(hf);
       }
       if ( !res )
       {
          return err_ret(-21, "Can't get time from file:", pt);
       }
    }

    ::SetLastError(0);
    if ( tmode == 't' ) // Stamp: [[CC]YY]MMDDhhmm[.SS]
    {
       i = gettimdata(pt, v, n100);
       if ( i < 1 )
       {
          return err_ret(-22, "Bad TimeStamp:", pt);
       }
       SYSTEMTIME st;
       ::GetLocalTime(&st);
       st.wMilliseconds       = 0;
       st.wSecond             = (WORD)(n100 / 100000);
       st.wMinute             = (WORD)v[i-1];
       if ( i > 1 ) st.wHour  = (WORD)v[i-2];
       if ( i > 2 ) st.wDay   = (WORD)v[i-3];
       if ( i > 3 ) st.wMonth = (WORD)v[i-4];
       if ( i > 5 ) st.wYear  = (WORD)(100*v[i-6] + v[i-5]);
       else if ( i == 5 ) { 
          int yy = v[i-5], YY00 = (int)st.wYear - st.wYear % 100;
          if ( yy > st.wYear - YY00 + 10) YY00 -= 100;
          st.wYear = (WORD)(YY00 + yy);
       }
       res = ::SystemTimeToFileTime(&st, &atime);
       if ( !res )
       {
          return err_ret(-22, "Bad TimeStamp:", pt);
       }
       ::LocalFileTimeToFileTime(&atime, &utime);
       atime = utime;
    }

    if ( pa ) // Adjust: [-][[hh]mm]SS[.sss]       
    {
       bool neg = false;
       if ( L'+' == *pa ) ++pa;
       if ( L'-' == *pa ) { neg = true; ++pa; }

       i = gettimdata(pa, v, n100);
       if ( i < 1 )
       {
          return err_ret(-23, "Bad AdjustTime:", pa);
       }
       __int64 v64 = v[i-1];
       if ( i > 1 ) v64 += v[i-2] * 60;
       if ( i > 2 ) v64 += v[i-3] * 60*60;
       if ( i > 3 ) v64 += v[i-4] * 60*60*24;

       llmuladd(v64, 10*1000*1000, n100);
       if ( neg ) v64 = -v64;

       *(__int64 *)(&atime) += v64;
       *(__int64 *)(&utime) += v64;
    }

    for ( i = 0; i < na && rc == 0; ++i )
    {
       hf = ::CreateFileW(args[i],
          GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
          NULL, nocreate || aonly || monly ? OPEN_EXISTING : CREATE_ALWAYS, 0,NULL
       );
       if ( INVALID_HANDLE_VALUE == hf )
       {
          if ( nocreate && ::GetLastError() == ERROR_FILE_NOT_FOUND )
             continue;
       }
       res = ::SetFileTime(hf, NULL, monly ? NULL : &atime, aonly ? NULL : &utime);
       if ( !res )
          rc = err_ret(-24, "Can't set file time :", args[i]);

       ::CloseHandle(hf);
    }
    return rc;
}

//======================================================================================
